// ttttttt.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "ttttttt.h"

#import "c:/Program Files/Common Files/System/ado/msado15.dll"           \
		no_namespace                     \
		rename( "EOF", "adoEOF" )


#include <ole2.h>
#include <stdio.h>
#include "conio.h"
#include "icrsint.h"


#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
#include "QtSql/qsql.h"
#include "QtSql/qsqldatabase.h"
#include "QtSql/qsqlquery.h"
#include "QtCore/qdebug.h"
#include <QtCore/QCoreApplication>

#pragma comment(lib, "odbc32.lib") 


#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// The one and only application object

CWinApp theApp;

using namespace std;


// This article says you need
// to link with odbc32.lib, but taking the next line of code OUT
// doesn't seem to harm anything
#pragma comment( lib, "odbc32.lib" )

bool CHECK( SQLRETURN rc, char * msg, bool printSucceededMsg=false, bool quit=true )
{
  if( SQL_SUCCEEDED( rc ) )
  {
    if( printSucceededMsg )  printf( "%s succeeded\n", msg ) ;
    
    return true ;
  }
  else
  {
    printf( "NO!!!  %s has FAILED!!\n", msg ) ;
    
    if( quit )  FatalAppExitA( 0, msg ) ;

    return false ;
  }
}




// class extracts empid, fname and lastname
class CEmployeeRs : public CADORecordBinding {
   BEGIN_ADO_BINDING(CEmployeeRs)

   // Column empid is the 1st field in the recordset   
   ADO_VARIABLE_LENGTH_ENTRY2(1, adVarChar, m_sz_empid, sizeof(m_sz_empid), 
                              lemp_empidStatus, TRUE)

   ADO_VARIABLE_LENGTH_ENTRY2(2, adVarChar, m_sz_fname, sizeof(m_sz_fname), 
                              lemp_fnameStatus, TRUE)

   ADO_VARIABLE_LENGTH_ENTRY2(4, adVarChar, m_sz_lname, sizeof(m_sz_lname), 
                              lemp_lnameStatus, TRUE)

   END_ADO_BINDING()

public:
   CHAR m_sz_empid[10];
   ULONG lemp_empidStatus;
   CHAR m_sz_fname[40];
   ULONG lemp_fnameStatus;
   CHAR m_sz_lname[41];
   ULONG lemp_lnameStatus;
};

// Function declaration
inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); };
void AddNewX();
void PrintProviderError(_ConnectionPtr pConnection);

inline int myscanf(char* strDest, int n) {    
   char strExBuff[10];
   char* pstrRet = fgets(strDest, n, stdin);

   if (pstrRet == NULL)
      return 0;

   if (!strrchr(strDest, '\n'))
      // Exhaust the input buffer.
      do {
         fgets(strExBuff, sizeof(strExBuff), stdin);
      } while (!strrchr(strExBuff, '\n'));
   else
      // Replace '\n' with '\0'
      strDest[strrchr(strDest, '\n') - strDest] = '\0';

   return strlen(strDest);
}


void status( SQLSMALLINT handleType, SQLHANDLE theHandle, int line )
{
  SQLCHAR sqlState[6];
  SQLINTEGER nativeError;
  SQLCHAR msgStr[256];
  SQLSMALLINT overBy ; // the number of characters that msgStr buffer was TOO SHORT..
  
  // http://msdn.microsoft.com/en-us/library/ms716256(VS.85).aspx
  // This must be the WEIRDEST ERROR REPORTING FUNCTION I've EVER seen.
  // It requires 8 parameters, and its actually pretty .. silly
  // about the amount of state information it expects YOU to keep track of.

  // It isn't so much a "GetLastError()" function
  // as it is a "GetMeStatus( something very, very specific )" function.
  
  SQLRETURN retCode ;
  
  for( int i = 1 ; i < 20 ; i++ )
  {
    retCode = SQLGetDiagRecA(
      
      handleType,  // the type of object you're checking the status of
      theHandle,   // handle to the actual object you want the status of
      
      i, // WHICH status message you want.  The "Comments" section at the 
      // bottom of http://msdn.microsoft.com/en-us/library/ms716256(VS.85).aspx
      // seems to explain this part well.

      sqlState,    // OUT:  gives back 5 characters (the HY*** style error code)
      &nativeError,// numerical error number
      msgStr,      // buffer to store the DESCRIPTION OF THE ERROR.
      // This is the MOST important one, I suppose

      255,         // the number of characters in msgStr, so that
      // the function doesn't do a buffer overrun in case it
      // has A LOT to tell you
      &overBy      // again in case the function has A LOT to tell you,
      // the 255 character size buffer we passed might not be large
      // enough to hold the entire error message.  If that happens
      // the error message will truncate and the 'overBy' variable
      // will have a value > 0 (it will measure number of characters
      // that you 'missed seeing' in the error message).
      
    ) ;

    if( CHECK( retCode, "SQLGetDiagRecA" ) )
    {
      printf( "LINE %d:  [%s][%d] %s\n", line, sqlState, nativeError, msgStr ) ;
    }
    else
    {
      // Stop looping when retCode comes back
      // as a failure, because it means there are
      // no more messages to tell you
      break ;
    }
  }
}


void useQT()
{
	QCoreApplication::addLibraryPath ( "D:\\Librarys\\Qt\\5.3.0\\5.3\\msvc2010_opengl\\plugins\\sqldrivers" );

	QStringList ssss = QCoreApplication::libraryPaths ();
	qDebug() << ssss;

	QString addpath;

 QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "mysql");

    db.setUserName("root");
	db.setPort( 3306 );

#if 0
    db.setHostName("192.168.1.50");
    db.setDatabaseName("hlm");
    db.setPassword("hlmpw");
#else
    db.setHostName("192.168.1.4");
    db.setDatabaseName("mysql");
    db.setPassword("lodics00");
#endif

	//QPluginLoader loader("");

    if (!db.open()) {

		qDebug() << "drivers: "<< QSqlDatabase::drivers();

        //QMessageBox::critical(0, tr("Cannot open database"),
        //    tr("Unable to establish a database connection.\n"
        //       "This example needs SQLite support. Please read "
        //       "the Qt SQL driver documentation for information how "
        //       "to build it."), QMessageBox::Cancel);
        return;
    }

    QSqlQuery query;
    query.exec("create table person (id int primary key, "
               "name varchar(20), address varchar(200), typeid int)");
    query.exec("insert into person values(1, 'Alice', "
               "'<qt>123 Main Street<br/>Market Town</qt>', 101)");
    query.exec("insert into person values(2, 'Bob', "
               "'<qt>PO Box 32<br/>Mail Handling Service"
               "<br/>Service City</qt>', 102)");
    query.exec("insert into person values(3, 'Carol', "
               "'<qt>The Lighthouse<br/>Remote Island</qt>', 103)");
    query.exec("insert into person values(4, 'Donald', "
               "'<qt>47338 Park Avenue<br/>Big City</qt>', 101)");
    query.exec("insert into person values(5, 'Emma', "
               "'<qt>Research Station<br/>Base Camp<br/>"
               "Big Mountain</qt>', 103)");
}


void useSQLAPI_Test()
{


// Following this example, just adding a bit more
  // color and making it work with our specific example.
  
  
  // 1.  Create a handle for the environment.
  SQLHANDLE hEnv ;
  SQLRETURN retCode ;
  
  retCode = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv ) ;
  
  CHECK( retCode, "allocate environment handle" ) ;
  

  // 2.  Next, set the version of ODBC to use to ODBC version 3.
  // Format of this command is a bit weird, we cast the value we're passing
  // to (void*) because the function requires it, but then we say that the
  // length of the "string" we've passed in is 0 characters long, so I assume
  // that means SQLSetEnvAttr should know to interpret the "pointer value" that
  // we passed in as actually an integer value (which is what it is).
  retCode = SQLSetEnvAttr( hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, SQL_IS_INTEGER ) ; 

  CHECK( retCode, "setting the environment attribute setting to ODBC version 3" ) ;


  // 3.  Allocate the connection handle.  Note this doesn't
  // connect us to the database YET.  We're still "ALLOCATING",
  // whatever that means :) (Hey i know what allocating is,
  // but this is an awful number of steps to follow if you
  // ask me, microsoft!!  Whatever happened to a simple init()
  // function?
  SQLHANDLE hConn ;

#if 0
  CHECK( SQLAllocHandle( SQL_HANDLE_DBC, hEnv, &hConn ), "allocate handle" ) ;
#else
  SQLHDBC hdbc;
  if( SQLAllocHandle( SQL_HANDLE_DBC, hEnv, &hdbc ) != SQL_SUCCESS )
	  return;

  SQLCHAR* ODBC_Name = (SQLCHAR*)"test_maria_32";
  retCode = SQLConnectA( hdbc, ODBC_Name, SQL_NTS, (SQLCHAR*)"root", SQL_NTS, (SQLCHAR*)"lodics00", SQL_NTS );
  if( !CHECK( retCode, "SqlConnectA", false ) )
  {
    // if this fails, I want that extra status
    // information about WHY the failure happened.
    // status function is defined above.
    
    status( SQL_HANDLE_DBC, hConn, __LINE__ ) ;
  }

#endif

  
  

  // HOOK IT UP!!  Actually connect to the database.
  SQLCHAR* dsnName = (SQLCHAR*)"ODBC_HLM_user" ;  // MUST BE THE SAME
  // as the name of the ODBC data source you set
  // in the Microsoft ODBC Administrator window.

  SQLCHAR* userid = (SQLCHAR*)"root";
  SQLCHAR* password = (SQLCHAR*)"hlmpw";  // using a BLANK
  // Above are my own correct userid and password credentials to
  // be used when logging into the MySQL database server.

  // 4.  Open database connection.
  retCode = SQLConnectA(
    
    hConn,
    
    dsnName,  // name of data source we are connecting to,
    // AS PER REGISTERED IN ODBC Data Source Administrator.

    // If you are on a 64-bit machine, and you
    // DO NOT USE THE 64-bit driver.  As long as
    // your compiler publishes a 32-bit .exe (which
    // Visual Studio does), you'll keep getting:

    // [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified

    // SO DON'T USE THE 64-BIT DRIVERS!  Instead, install
    // the 32-bit driver, and then managing/using
    // your 32-bit datasources in
    // c:\windows\syswow64\odbcad32.exe

    // Note that on a 64-bit windows machine, the 32-bit
    // drivers and the 64-bit drivers are managed
    // from COMPLETELY SEPARATE, BUT IDENTICAL-LOOKING
    // windows.  Its really weird.

    // On a 64-bit machine:
    // c:\windows\system32\odbcad32.exe    // 64-bit version [even though it SAYS system32_ in the path, this is the __64__ bit version on a 64-bit machine]
    // c:\windows\syswow64\odbcad32.exe    // 32-bit version [even though it SAYS syswow64_ in the path]

    // Call it stupid, scream, pull your hair out,
    // that's what it is.
    // http://stackoverflow.com/questions/949959/why-do-64bit-dlls-go-to-system32-and-32bit-dlls-to-syswow64-on-64bit-windows

    // and

    // http://blogs.sepago.de/helge/2008/04/20/windows-x64-all-the-same-yet-very-different-part-7/

    // Thanks again, Microsoft,
    // for making the 64-bit programming experience
    // such a pleasure.
    SQL_NTS,  // the DSN name is a NULL TERMINATED STRING, so "count it yourself"

    userid,
    SQL_NTS,  // userid is a null-terminated string
    
    password,
    SQL_NTS   // password is a null terminated string
    
  ) ;


  if( !CHECK( retCode, "SqlConnectA", false ) )
  {
    // if this fails, I want that extra status
    // information about WHY the failure happened.
    // status function is defined above.
    
    status( SQL_HANDLE_DBC, hConn, __LINE__ ) ;
  }



// 6.  Create and allocate a statement
  SQLHANDLE hStmt ;
  CHECK( SQLAllocHandle( SQL_HANDLE_STMT, hConn, &hStmt ), "allocate handle for statement" ) ;

  
  // 7.  Form a query to run and attach it to the hStmt
  // this basically connects the hStmt up with
  // some results.
  SQLCHAR* query = (SQLCHAR*)"SELECT * from user" ;
  CHECK( SQLExecDirectA( hStmt, query, SQL_NTS ), "execute query" ) ;


  // 8.  Read data results that are now in the hStmt.
  retCode = SQLFetch( hStmt ) ;

  CHECK( retCode, "first sqlFetch" ) ;

  // How many rows got returned?
  SQLLEN numRows ;
  retCode = SQLRowCount( hStmt, &numRows ) ;
  printf( "%d rows were fetched, ruff.\n", numRows ) ;

  // With a query like the one we wrote (SELECT *),
  // we don't know how many columsn should be in
  // the result set at this point.
  // So we ask ODBC to tell us!
  SQLSMALLINT numCols ;
  retCode = SQLNumResultCols( hStmt, &numCols ); // SqlNumResultCols

  // Now print the column names.
  // SQLDescribeCol function
  SQLCHAR colName[ 256 ] ;
  
  SQLSMALLINT colNameLen, dataType, numDecimalDigits, allowsNullValues ;
  SQLUINTEGER columnSize ;

  for( int i = 1 ; i <= numCols ; i++ )
  {
    retCode = SQLDescribeColA( hStmt, i, colName, 255, &colNameLen, &dataType, &columnSize, &numDecimalDigits, &allowsNullValues ) ;
    if( CHECK( retCode, "SQLDescribeCol" ) )
    {
      printf( "Column #%d: '%s', datatype=%d size=%d decimaldigits=%d nullable=%d\n",
                       i,colName,   dataType, columnSize,  numDecimalDigits, allowsNullValues ) ;
    }
  }

  for( int i = 1 ; i <= numRows ; i++ )
  {
    // Datatypes
    // SQLGetData

    char buf[256];
    SQLINTEGER numBytes ;

    for( int j = 1 ;   // column counter starts at __1__, not 0.
      j <= numCols ;   // numCols retrieved above
      j++ )
    {
      retCode = SQLGetData(
      
        hStmt,
        j,           // COLUMN NUMBER of the data to get
        SQL_C_CHAR,  // the data type that you expect to receive
        buf,         // the place to put the data that you expect to receive
        255,         // the size in bytes of buf (-1 for null terminator)
        &numBytes    // size in bytes of data returned
      
      ) ;

      if( CHECK( retCode, "SqlGetData", false ) )
      {
        // Print the data we got.
        printf( "%10s", buf ) ;
      }
    
    } // end for.

    puts("");

    // Try and fetch the next result.
    // fall out of the loop if fetch fails
    // (meaning, no more data to get)
    retCode = SQLFetch( hStmt ) ;
    if( !SQL_SUCCEEDED( retCode ) )
    {
      // SQLFetch FAILS AS IT FETCHES the last row of data.
      printf( "And %d is the LAST row.. we're not getting any more after this one\n", i ) ;
    }
  }

  // So we used a FOR loop above to fetch
  // exactly numRows rows from the results.

  // The other (perhaps more common) 
  // way to do this is to use a loop like
  
  // while( SQL_SUCCEEDED( SQLFetch( hStmt ) ) )
  // {
  //   // Work with result data
  // }

  // When we do it that way, 
  // WE EXPECT/RELY ON SQLFetch TO TELL US
  // IT FAILED when we've reached the
  // LAST row of data.

  // free up resources.
  SQLFreeHandle( SQL_HANDLE_STMT, hStmt ) ;
  SQLFreeHandle( SQL_HANDLE_DBC, hConn ) ;
  SQLFreeHandle( SQL_HANDLE_ENV, hEnv ) ;


}


void useTEST()
{
#if 0
    SQLHENV henv;
    SQLHDBC hdbc;
    SQLHSTMT hstmt;
    SQLRETURN retcode;

    SQLWCHAR OutConnStr[255];
    SQLSMALLINT OutConnStrLen;

    // Allocate environment handle
    retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);

    // Set the ODBC version environment attribute
    if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
        retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); 

        // Allocate connection handle
        if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
            retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); 

             // Set login timeout to 5 seconds
            if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
                SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);

                // Connect to data source
                retcode = SQLDriverConnect(
                    hdbc, 
                    0,
                    (SQLWCHAR*)L"DSN=ODBC_HLM_user;SERVER=localhost;UID=root;PWD=hlmpw;DRIVER=MySQL Server;", 
                    _countof(L"DSN=ODBC_HLM_user;SERVER=localhost;UID=root;PWD=hlmpw;DRIVER=MySQL Server;"),
                    OutConnStr,
                    255, 
                    &OutConnStrLen,
                    SQL_DRIVER_COMPLETE );

                // Allocate statement handle
                if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
                    retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); 

                    // Process data
                    retcode = SQLExecDirect(hstmt, (SQLWCHAR*)L"SELECT TEST_STRING, TEST_INTEGER, TEST_FLOAT FROM dbo.testfire", SQL_NTS);

                    if (retcode == SQL_SUCCESS) {
                        SQLINTEGER sTestInt, cbTestStr, cbTestInt, cbTestFloat, iCount = 1;
                        SQLFLOAT dTestFloat;
                        SQLCHAR szTestStr[200];
                        while (TRUE) {
                            retcode = SQLFetch(hstmt);
                            if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {
                                cout<<"An error occurred";
                            }
                            if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){

                                SQLGetData(hstmt, 1, SQL_C_CHAR, szTestStr, 200, &cbTestStr);
                                SQLGetData(hstmt, 2, SQL_C_ULONG, &sTestInt, 0, &cbTestInt);
                                SQLGetData(hstmt, 3, SQL_C_DOUBLE, &dTestFloat, 0,&cbTestFloat);

                                /* Print the row of data */
                                cout<<"Row "<<iCount<<":"<<endl;
                                cout<<szTestStr<<endl;
                                cout<<sTestInt<<endl;
                                cout<<dTestFloat<<endl;
                                iCount++;
                            } else {
                                break;
                            }
                        }
                    }else{
                        cout<<"Query execution error."<<endl;
                    }

                    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
                    SQLDisconnect(hdbc);
                }else{ 
                    cout<<"Connection error"<<endl;
                }
                SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
            }
        }
        SQLFreeHandle(SQL_HANDLE_ENV, henv);
    }
#endif
}


int useADO_Test()
{
   HRESULT hr = S_OK;
   if (FAILED(::CoInitialize(NULL))) 
      return -1;

   if (SUCCEEDED(hr)) {
      AddNewX();
      ::CoUninitialize();
   }

   return 0;
}


int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	QCoreApplication a(argc, argv);

	int nRetCode = 0;

	HMODULE hModule = ::GetModuleHandle(NULL);

	if (hModule != NULL)
	{
		// initialize MFC and print and error on failure
		if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0))
		{
			// TODO: change error code to suit your needs
			_tprintf(_T("Fatal Error: MFC initialization failed\n"));
			nRetCode = 1;
		}
		else
		{
			// TODO: code your application's behavior here.
		}
	}
	else
	{
		// TODO: change error code to suit your needs
		_tprintf(_T("Fatal Error: GetModuleHandle failed\n"));
		nRetCode = 1;
	}


	//useSQLAPI_Test();
	//useADO_Test();
	//useTEST();
	useQT();

	return nRetCode;
}



void AddNewX() {
   // Define ADO object pointers. Initialize pointers on define.
   _RecordsetPtr pRstEmployees = NULL;
   _ConnectionPtr pConnection = NULL;

   // Define Other variables
   IADORecordBinding *picRs = NULL;   // Interface Pointer declared.(VC++ Extensions)   
   CEmployeeRs emprs;   // C++ class object   

   HRESULT hr = S_OK;

   // Replace Data Source value with your server name.
   _bstr_t strCnn("Provider='MySqlProv'; Data Source='test_maria_32'; Password='lodics00'; User ID='root'; Location=192.168.1.4");
   _bstr_t strId;
   _bstr_t strMessage;

   try {
      // Open a connection
      TESTHR(pConnection.CreateInstance(__uuidof(Connection)));
      pConnection->Open(strCnn, "", "", adConnectUnspecified);

      // Open employee table 
      TESTHR(pRstEmployees.CreateInstance(__uuidof(Recordset)));

      // You have to explicitly pass the Cursor type and LockType to the Recordset here
      pRstEmployees->Open("employee", _variant_t((IDispatch *) pConnection, true),
                          adOpenKeyset, adLockOptimistic, adCmdTable);

      // Open IADORecordBinding interface pointer for Binding Recordset to a class    
      TESTHR(pRstEmployees->QueryInterface(__uuidof(IADORecordBinding), (LPVOID*)&picRs));

      // Bind the Recordset to a C++ Class here   
      TESTHR(picRs->BindToRecordset(&emprs));         

      // Get data from the user.The employee id must be formatted as first,middle and last 
      // initial, five numbers,then M or F to signify the gender. For example, the 
      // employee id for Bill A. Sorensen would be "BAS55555M". 
      printf("Enter Employee Id: ");
      myscanf(emprs.m_sz_empid, sizeof(emprs.m_sz_empid));
      strId = emprs.m_sz_empid;
      printf("Enter First Name: ");
      myscanf(emprs.m_sz_fname, sizeof(emprs.m_sz_fname));
      printf("Enter Last Name:");
      myscanf(emprs.m_sz_lname, sizeof(emprs.m_sz_lname));

      // Proceed if user entered id, the first and the last name. 

      if (strcmp(emprs.m_sz_empid, "") && strcmp(emprs.m_sz_fname, "") && strcmp(emprs.m_sz_lname, "")) {
         // This adds a new record to the table   
         // if (FAILED(hr = picRs->AddNew(&emprs)))
         //_com_issue_error(hr);
         TESTHR(picRs->AddNew(&emprs));

         // Show the newly added data
         printf("New Record: %s  %s  %s \n",\
            emprs.lemp_empidStatus == adFldOK ? emprs.m_sz_empid : "<NULL>",\
            emprs.lemp_fnameStatus == adFldOK ? emprs.m_sz_fname : "<NULL>",\
            emprs.lemp_lnameStatus == adFldOK ? emprs.m_sz_lname : "<NULL>");
      }
      else
         printf("Please enter an employee id, first name and last name.\n");    

      // Delete the new record because this is a demonstration. 
      pConnection->Execute("DELETE FROM EMPLOYEE WHERE emp_id = '" + strId + "'", NULL, adCmdText);
   }
   catch(_com_error &e) {
      // Notify the user of errors if any.
      _variant_t vtConnect = pRstEmployees->GetActiveConnection();

      // GetActiveConnection returns connect string if connection
      // is not open, else returns Connection object.
      switch(vtConnect.vt) {
         case VT_BSTR:
            printf("Error:\n");
            printf("Code = %08lx\n", e.Error());
            printf("Message = %s\n", e.ErrorMessage());
            printf("Source = %s\n", (LPCSTR) e.Source());
            printf("Description = %s\n", (LPCSTR) e.Description());
            break;
         case VT_DISPATCH:
            PrintProviderError(vtConnect);
            break;
         default:
            printf("Errors occured.");
            break;
      }
   }

   // Clean up objects before exit.  Release the IADORecordset Interface here   
   if (picRs)
      picRs->Release();

   if (pRstEmployees)
      if (pRstEmployees->State == adStateOpen)
         pRstEmployees->Close();
   if (pConnection)
      if (pConnection->State == adStateOpen)
         pConnection->Close();
}

void PrintProviderError(_ConnectionPtr pConnection) {
   // Print Provider Errors from Connection object.
   // pErr is a record object in the Connection's Error collection.
   ErrorPtr pErr = NULL;
   long nCount = 0;    
   long i = 0;

   if ( (pConnection->Errors->Count) > 0) {
      nCount = pConnection->Errors->Count;

      // Collection ranges from 0 to nCount -1.
      for (i = 0 ; i < nCount ; i++) {
         pErr = pConnection->Errors->GetItem(i);
         printf("\n\t Error number: %x\t%s", pErr->Number, (LPCSTR)pErr->Description);
      }
   }
}